<?php
require_once('Board.php');
require_once('Player.php');
require_once('Dice.php');
require_once('BoardCell.php');
require_once('JailCell.php');
require_once('PropertyCell.php');
require_once('IncomeTaxCell.php');
require_once('UtilityCell.php');
require_once('RailroadCell.php');
require_once('Card.php');
require_once('ChanceCard.php');

class Game {
    const BOARD_CONFIG = 'cell_config.json';
	const PLAYER_CONFIG = 'players_config.xml';
	const CARDS_CONFIG = 'cards_config.json';
	const MORTGAGE_PERCENTAGE = 0.9;
	const JAIL_RELEASE_FEE = 50;
	private $numOfPlayers;
	private $board;
	private $player_data = array();
	private $rounds_completed;
	private $history = array();
	private $cards = array();

//Setup

    public function __construct($players = 2) 
    {	
		if($players < 2 || $players > 8)
		{
			echo "Error: The game must have a minimum of 2 players and a maximum of 8.\n";
			return;
		}
		
		$this->numOfPlayers   = $players;
		
		$this->initalizeGame();
    }

    public function initalizeGame() 
    {	
        $cells = $this->loadCells();
		$this->board = new Board($cells);
		
		$start_location = $this->board->getCellByIndex(0);
		
		$possiblePlayers = $this->loadPlayers();
		$tmpPlayers = array();
		for ($count =0; $count < $this->numOfPlayers; $count++)
		{
			$player = new Player($possiblePlayers[$count], $start_location, $this->board);
			$tmpPlayers[$count] = array('player' 	  => $player,
										'turns_taken' => 0);
		}
		
		$this->player_data = $this->determinePlayerOrder($tmpPlayers);
		
		$this->cards = $this->loadCards();
    }

	/**
	 * This will read the specified file and return the contents as an array.
	 * @return 
	 */
    public static function loadCells() 
	{
    	$cells = file_get_contents(Game::BOARD_CONFIG);
		
		$json_array = json_decode($cells, true);
		$cell_array = $json_array['cells'];

		$board_cells = array();
		foreach ($cell_array as $cell) {		
			$class = $cell['type']."Cell";
			$board_cells[] = new $class($cell);
		}

        return $board_cells;
    }
	
	/**
	 * This will read the specified file and return the contents as an array.
	 * 
	 * @return 
	 */	
	public function loadPlayers()
	{
		$players = simplexml_load_file(Game::PLAYER_CONFIG);
		$player_array = array();		
		foreach($players->player as $p){
			$player_data = $p->name;
			$player_array[] = (string)$player_data;
		}
        return $player_array;
	}
	
	/**
	 * This will read the specified file and return the contents as an array.
	 * @return 
	 */
    public static function loadCards() 
	{
    	$cells = file_get_contents(Game::CARDS_CONFIG);
		
		$json_array = json_decode($cells, true);
		$card_array = $json_array['cards'];

		$card_cells = array();
		foreach ($card_array as $cell) {		
			$class = $cell['type']."Card";
			$card_cells[] = new $class($cell);
		}

        return $card_cells;
    }
	
	/**
	 * This will set the players to a random starting order.
	 * @param object $players
	 * @return an array of players in a random order.
	 */
	public function determinePlayerOrder($players){
		$res_players = array();
		$Keys = array_keys($players);
		shuffle($Keys);
		foreach($Keys as $p)
		{
			$res_players[] = $players[$p];
		}
		return $res_players;
	}
	
//Getters
	
	/**
	 * This will return the array of current players
	 * @return 
	 */
	public function getPlayers() 
	{
		return $this->player_data;	
	}

	public function getBoard()
	{
		return $this->board;
	}
	
	/**
	 * This returns the array of card objects.
	 * @return 
	 */
	public function getCards()
	{
		return $this->cards;
	}

	public function getNumberOfPlayers(){
		return $this->numOfPlayers;
	}

	/**
	 * This will return a specific player that matches the passed name.
	 * @param object $playerName
	 * @return The matched player.
	 */
	public function getPlayerByName($playerName)
	{
		$player = NULL;
		foreach($this->player_data as $play)
		{
			if($play['player']->getName() == $playerName)
			{
				$player = $play['player'];
			}
		}
		return $player;
	}

//Setters

	public function setNumberOfPlayers($playerNum){
		$this->numOfPlayers = $playerNum;
	}

//User Input
	
	/**
	 * This function is where the player selects what they want to do.
	 * @param object $message This is the message to display to the user
	 * @param object $values This is an array or user inputs that is allowed.
	 * @return The valid user input.
	 */
	public function playerInput($message, $values)
	{
		$result = 0;
		while($result == 0)
		{
			printf($message);
			$value = trim(fgets(STDIN));
			if(in_array($value, $values))
			{
				$result = 1;
			}
		}
		return $value;
	}
	
//Mortgage
	
	/**
	 * This function will ask the current player if they want to mortage a cell.
	 * @param object $player
	 * @return 
	 */
	public function mortgageAction($player)
	{
		if(count($player->getOwnedCells()) > 0)
		{
			$message = "Do you want to Mortgage a property?(y/n)";
			$values = array("y", "n");
			$value = $this->playerInput($message, $values);
			if($value == "y")
			{
				$this->mortgageCell($player);
			}
		}
	}
	
	/**
	 * This function is where a user is able to mortgage a property they own.
	 * @param object $player
	 * @return will return nothing.
	 */
	public function mortgageCell($player)
	{
		$player_cells = $player->getOwnedCells();
		printf ("Your Owned Cells are: \n");
		$cell_position = 1;
		$values = array();
		foreach($player_cells as $cell)
		{
			if(!$cell->isMortgaged())
			{
				printf("\t%d:\t%s\n", $cell_position, $cell->getName());
			}
			else
			{
				printf("\t%d:\t%s\n", $cell_position, $cell->getName()." - is Mortgaged already!");
			}
			$values[] = $cell_position;
			$cell_position++;
		}
		printf ("\t%d:\tExit\n", $cell_position);
		$values[] = $cell_position;
		$message = "Which cell would you like to mortgage? (1-".$cell_position.")";
		$result = $this->playerInput($message, $values);
		if($result != $cell_position)//This means not exit
		{
			$cell = $player_cells[$result-1];
			$player->mortgageCell($cell);
		}			
	}
		
//Jail
	
	/**
	 * This function will handle the players actions during the current round because he is in jail.
	 * 
	 * @param object $player
	 * @param object $dice
	 * @return True if still in jail, or false if not
	 */
	public function playerInJail($player, $result, $key, $jail_cell)//public function playerInJail($player, $dice, $key, $jail_cell)
	{
		//@TODO:If the user does not have enough money to pay there way out of jail....
		
		
		$player_cards = $player->getCards();
		$found = false;
		foreach($player_cards as $p_card)
		{
			if($p_card->getName() == "Get Out of Jail Free"){
				$found = true;
			}
		}
		if(count($player_cards) > 0 && found)
		{
			$value = array_search("Get Out of Jail Free", $player_cards);
			printf("You are in Jail!\n1. Pay 50 dollars?\n2. Use your 'Get Out of Jail Free' card?\n3. Neither just roll the dice.\n");
			$message = "What would you like to do?(1-3)";
			$values = array("1", "2", "3");
			$player_result = $this->playerInput($message, $values);
			
			if($player_result == 1)
			{
				$this->releaseFromJailByPaying($player, $result, $key, $jail_cell);
			}
			elseif($player_result == 2)
			{
				$this->releaseFromJailByUsingJailCard($player, $result, $key, $value, $jail_cell);
			}
			else
			{
				$this->rollFromJail($player, $result, $key, $jail_cell);
			}
		}
		else //This means that they do not have a Get Out of Jail free card
		{
			printf("You are in Jail!\n1. Pay 50 dollars?\n2. Just roll the dice.\n");
			$message = "What would you like to do?(1-2)";
			$values = array("1", "2");
			$player_result = $this->playerInput($message, $values);
			
			if($player_result == 1)
			{
				$this->releaseFromJailByPaying($player, $result, $key, $jail_cell);
			}
			else
			{
				$this->rollFromJail($player, $result, $key, $jail_cell);
			}			
		}		 
	}
	
	/**
	 * This function will handle releasing a player from jail by the player paying the fee.
	 * @param object $player		The player who is in jail
	 * @param object $result		The roll of the dice
	 * @param object $key			The index of the player in the payers array
	 * @param object $jail_cell		The jail cell object.
	 * @return 
	 */
	public function releaseFromJailByPaying($player, $result, $key, $jail_cell)
	{
		$player->removeMoney(50);
		$jail_cell->releasePlayer($player);
		$this->roundActions($player, $result, $key);
	}
	
	/**
	 * This function will handle releasing a player from jail by the player using a Get Out of Jail Free card.
	 * @param object $player		The player who is in jail
	 * @param object $result		The roll of the dice
	 * @param object $key			The index of the player in the payers array
	 * @param object $car_location	The location of the card in the cards array for the current player.
	 * @param object $jail_cell		The jail cell object.
	 * @return 
	 */	
	public function releaseFromJailByUsingJailCard($player, $result, $key, $card_location, $jail_cell)
	{
		$player->removeCardByIndex($card_location);
		$jail_cell->releasePlayer($player);
		$this->roundActions($player, $result, $key);
	}
	
	/**
	 * This function will handle the player trying to roll his way out of jail
	 * @param object $player		The player who is in jail
	 * @param object $result		The roll of the dice
	 * @param object $key			The index of the player in the payers array
	 * @param object $jail_cell		The jail cell object.
	 * @return 
	 */
	public function rollFromJail($player, $result, $key, $jail_cell)
	{
		$jail_cell->increasePlayersRoundsInJail($player);
		if($player->getNumberOfDoubles() >= 1)
		{
			$jail_cell->releasePlayer($player);	//player correctly released from jail
			$this->roundActions($player, $result, $key); //This means that the player rolled doubles and is now out of jail.
		}
		elseif($jail_cell->getPlayersRoundsInJail($player) == 3)
		{
			$this->releaseFromJailByPaying($player, $result, $key, $jail_cell);//Means they need to pay and move out of jail right now.
		}
	}
		
//Game Actions
		
	/**
	 * This is the main functionality of the monopoly game. Each round is made up of each player getting the
	 * opportunity to do some actions.
	 */
	public function doRound()
	{	
		$dice = new Dice(2);
		$jail_cell = $this->board->getCellByName("Jail");
		foreach ($this->player_data as $key=>$currentPlayer)
		{			
			$passover_cells = array();
			$player = $this->player_data[$key]['player'];
			
			$player->setNumberOfDoubles(0);
			$this->mortgageAction($player);
			
			$player_cell = $player->getLocation()->getName();	
			$doubles = true;
			$times_rolled = 0;
			while($doubles)
			{	
				$result = $player->takeTurn($dice);
				$times_rolled ++;
				if($player_cell == $jail_cell->getName() && $jail_cell->inJail($player) == TRUE)
				{
					$this->playerInJail($player, $result, $key, $jail_cell);
				}
				else
				{
					$this->roundActions($player, $result, $key);
				}
				
				if(!$player->getRolledDoubles())
				{
					$doubles = false;
				}
/*				
				if($player->getRolledDoubles() && $times_rolled == 3)
				{
					//Rolled doubles 3 times in same turn.
					$player->setLocation($board->getCellByName("Jail"));
				}*/
			}
			$this->mortgageAction($player);
			$this->player_data[$key]['turns_taken']++;
		}
	}
	 
	/**
	 * This is the function that handles the players actions during a typical round when they are not in jail.
	 * @param object $player
	 * @param object $result
	 * @param object $key
	 */ 
	public function roundActions($player, $result, $key)
	{
		if($player->getNumberOfDoubles() == 3)
		{
			$player->setLocation($this->board->getCellByName("Jail"));
			$this->history[] = sprintf("%s\trolled doubles 3 times this turn, they are now in %s.", $player->getName(), $player->getLocation()->getName());
		}
		else
		{
			$start_location = $player->getLocation();
	
			for ($count = 0; $count < $result; $count++)
			{
				$current_location = $player->getLocation();
				$move_location_cell = $this->board->getNewLocationCell($current_location, 1);
				$player->passOver($move_location_cell);
			}
			
			$move_location_cell = $this->board->getNewLocationCell($current_location, 1);
			$player->landOn($move_location_cell);
			
			$player_location = $player->getLocation();
			
			
			$this->history[] = sprintf("%s\tstarted at\t%s\tand rolled a %d and landed on %s", $player->getName(), $start_location->getName(), $result, $move_location_cell->getName());
		}
	}
	
//Other

	public function printHistory() {
		foreach($this->history as $record) {
			echo "\n".$record;
		}	
	}
}